package com.specificgroup.jira.plugin.acpro.service.impl;

import com.specificgroup.jira.plugin.acpro.Constants;
import com.specificgroup.jira.plugin.acpro.exception.AcproException;
import com.specificgroup.jira.plugin.acpro.exception.AcproLoginException;
import com.specificgroup.jira.plugin.acpro.exception.AcproXmlException;
import com.specificgroup.jira.plugin.acpro.model.acpro.AcproLoginData;
import com.specificgroup.jira.plugin.acpro.service.AcproService;
import com.specificgroup.jira.plugin.acpro.util.HttpUtil;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.ACPro;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetAttachment;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetItem;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetProject;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetProjectItems;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetProjectList;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.GetUserList;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.Login;
import com.specificgroup.jira.plugin.acpro.xml.request.generated.ObjectFactory;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.ACProType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetAttachmentResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetItemResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetProjectItemsResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetProjectListResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetProjectResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.GetUserListResponseType;
import com.specificgroup.jira.plugin.acpro.xml.response.generated.LoginResponseType;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.log4j.Logger;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.List;

/**
 *
 * @author Ivan Holub
 */
public class AcproServiceImpl implements AcproService {

    private static final String STATUS_OK = "ok";

    private Logger log = Logger.getLogger(getClass());

    public void login(AcproLoginData acproLoginData) throws IOException, AcproException {
        String request = getLoginRequest(acproLoginData);
        String response = makeRequest(acproLoginData.getPostUrl(), request);

        ACProType acProType = unmarshalResponse(response);
        validateLoginResponse(acProType);
    }

    public String getLoginRequest(AcproLoginData acproLoginData) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);

        String request = marshalRequest(root);
        return request;
    }
    
    public GetProjectListResponseType makeGetProjectsRequest(AcproLoginData acproLoginData) throws IOException, AcproException {
        String request = getGetProjectListResponseRequest(acproLoginData);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetProjectListResponseType retValue = (GetProjectListResponseType) getResponse(acProType, GetProjectListResponseType.class);
        return retValue;
    }

    private void validateLoginResponse(ACProType acProType) throws AcproLoginException {
        boolean success = false;
        LoginResponseType response = null;
        List<Object> loginResponseOrGetUserListResponseOrGetProjectListResponse = acProType.getLoginResponseOrGetUserListResponseOrGetProjectListResponse();
        for (Object obj: loginResponseOrGetUserListResponseOrGetProjectListResponse) {
            if (obj instanceof LoginResponseType) {
                response = (LoginResponseType) obj;
                String status = response.getStatus();
                if (STATUS_OK.equals(status)) {
                    success = true;
                }
                break;
            }
        }
        if (!success) {
            String message;
            if (response != null) {
                message = response.getMessage();
            } else {
                message = "Login response is null";
            }
            throw new AcproLoginException(message);
        }
    }

    private Object getResponse(ACProType acProType, Class responseType) throws AcproException{
        validateLoginResponse(acProType);
        boolean success = false;
        Object retValue = null;
        List<Object> loginResponseOrGetUserListResponseOrGetProjectListResponse = acProType.getLoginResponseOrGetUserListResponseOrGetProjectListResponse();
        for (Object obj: loginResponseOrGetUserListResponseOrGetProjectListResponse) {
            if (responseType.equals(obj.getClass())) {
                retValue = obj;
                try {
                    String status = (String) PropertyUtils.getProperty(obj, "status");
                    if (STATUS_OK.equals(status)) {
                        success = true;
                    }
                } catch (IllegalAccessException e) {
                    //must not occur
                    log.error("Error", e);
                    throw new RuntimeException(e);
                } catch (InvocationTargetException e) {
                    //must not occur
                    log.error("Error", e);
                    throw new RuntimeException(e);
                } catch (NoSuchMethodException e) {
                    //must not occur
                    log.error("Error", e);
                    throw new RuntimeException(e);
                }
                break;
            }
        }
        if (!success || retValue == null) {
            throw new AcproException("Error getting Acpro response " + responseType.getSimpleName());
        }
        return retValue;
    }

    public String getGetProjectListResponseRequest(AcproLoginData acproLoginData) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetProjectList getProjectList = factory.createGetProjectList();
        root.getGetProjectList().add(getProjectList);
        String request = marshalRequest(root);
        return request;
    }
    
    public String makeRequest(String acproPostUrl, String request) throws IOException {
        String response = HttpUtil.post(acproPostUrl, request);
        return response;
    }
    
    public String getGetProjectRequest(AcproLoginData acproLoginData, String projectKey) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetProject getProject = factory.createGetProject();
        getProject.setProjectKey(projectKey);
        root.getGetProject().add(getProject);
        String request = marshalRequest(root);
        return request;
    }

    public GetProjectResponseType makeGetProjectRequest(AcproLoginData acproLoginData, String projectKey) throws IOException, AcproException {
        String request = getGetProjectRequest(acproLoginData, projectKey);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetProjectResponseType retValue = (GetProjectResponseType) getResponse(acProType, GetProjectResponseType.class);
        return retValue;
    }

    public GetProjectItemsResponseType makeGetProjectItemsRequest(AcproLoginData acproLoginData, String projectKey, String startIndex,
                                            String count) throws IOException, AcproException {
        String request = getGetProjectItemsRequest(acproLoginData, projectKey, startIndex, count);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetProjectItemsResponseType retValue = (GetProjectItemsResponseType) getResponse(acProType, GetProjectItemsResponseType.class);
        return retValue;
    }

    public String getGetProjectItemsRequest(AcproLoginData acproLoginData, String projectKey, String startIndex,
                                            String count) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetProjectItems getProjectItems = factory.createGetProjectItems();
        getProjectItems.setProjectKey(projectKey);
        getProjectItems.setStartIndex(startIndex);
        getProjectItems.setCount(count);
        root.getGetProjectItems().add(getProjectItems);

        String request = marshalRequest(root);
        return request;
    }

    private String marshalRequest(ACPro root) {
        try {
            JAXBContext context = JAXBContext.newInstance(Constants.XML_REQUEST_GENERATED_PACKAGE);
            Marshaller marshaller = context.createMarshaller();
            StringWriter writer = new StringWriter();
            marshaller.marshal(root, writer);
            String query = writer.toString();
            return query;
        } catch (JAXBException e) {
            //must not occur
            log.error("Error", e);
            throw new RuntimeException(e);
        }
    }

    private ACProType unmarshalResponse(String response) throws AcproXmlException {
        try {
            JAXBContext context = JAXBContext.newInstance(Constants.XML_RESPONSE_GENERATED_PACKAGE);
            Unmarshaller u = context.createUnmarshaller();
            StringReader reader = new StringReader(response);
            Object obj = u.unmarshal(reader);
            reader.close();            
            if (!(obj instanceof JAXBElement)) {
                throw new AcproXmlException("Acpro response xml is not valid");
            }
            JAXBElement root = (JAXBElement) obj;
            Object value = root.getValue();
            if (!(value instanceof ACProType)) {
                throw new AcproXmlException("Acpro response xml is not valid");
            }
            ACProType acProType = (ACProType) value; 
            return acProType;
        } catch (JAXBException e) {
            log.error("Error", e);
            throw new AcproXmlException("Acpro response xml is not valid");
        }
    }

    public String getGetItemRequest(AcproLoginData acproLoginData, String itemKey) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetItem getItem = factory.createGetItem();
        getItem.setItemKey(itemKey);
        root.getGetItem().add(getItem);

        String request = marshalRequest(root);
        return request;
    }

    public GetItemResponseType makeGetItemRequest(AcproLoginData acproLoginData, String itemKey) throws IOException, AcproException {
        String request = getGetItemRequest(acproLoginData, itemKey);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetItemResponseType retValue = (GetItemResponseType) getResponse(acProType, GetItemResponseType.class);
        return retValue;
    }

    public String getGetUserListRequest(AcproLoginData acproLoginData) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetUserList getUserList = factory.createGetUserList();
        root.getGetUserList().add(getUserList);

        String request = marshalRequest(root);
        return request;
    }

    public GetUserListResponseType makeUserListRequest(AcproLoginData acproLoginData) throws IOException, AcproException {
        String request = getGetUserListRequest(acproLoginData);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetUserListResponseType retValue = (GetUserListResponseType) getResponse(acProType, GetUserListResponseType.class);
        return retValue;
    }

    public GetAttachmentResponseType makeGetAttachmentRequest(AcproLoginData acproLoginData, String itemKey, String filename) throws IOException, AcproException {
        String request = getGetAttachmentRequest(acproLoginData, itemKey, filename);
        String response = makeRequest(acproLoginData.getPostUrl(), request);
        ACProType acProType = unmarshalResponse(response);
        GetAttachmentResponseType retValue = (GetAttachmentResponseType) getResponse(acProType, GetAttachmentResponseType.class);
        return retValue;
    }

    public String getGetAttachmentRequest(AcproLoginData acproLoginData, String itemKey, String filename) {
        ObjectFactory factory = new ObjectFactory();
        ACPro root = factory.createACPro();
        Login login = factory.createLogin();
        login.setUserid(acproLoginData.getUsername());
        login.setPassword(acproLoginData.getPassword());
        root.setLogin(login);
        GetAttachment getAttachment = factory.createGetAttachment();
        getAttachment.setItemKey(itemKey);
        getAttachment.setFileName(filename);
        root.getGetAttachment().add(getAttachment);
        String request = marshalRequest(root);
        return request;
    }

}
